#pragma once
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include<iostream>
#include<sys/select.h>
#include<unistd.h>
#include"Sock.hpp"
using namespace std;

void Usage(std::string proc)
{
  //cout <<"Usage:\n\t" << proc <<" select_port" << endl; 
  LOG(ERROR) <<"Usage:\t" << proc <<" select_port" << endl;
  exit(1);
}

//select的参数采用的是fd_set的类型变量，本质是一个位图，
//比特位的位置表示对应的fd，比特位的值1/0：表示是否需要关注该fd上的事件
//所以select最多可以监视的fd个数取决于fd_set类型变量的比特位的个数：sizeof(fd_set)*8
#define WATCH_MAX (sizeof(fd_set)*8) 
#define INVALID_FD -1 //表示无效的fd

//通过./select_test port启动select服务器
int main(int argc,char* argv[])
{
  if(argc != 2)
  {
    Usage(argv[0]);
  }
  //1.服务器的初始化：创建套接字，绑定，监听
  uint16_t port = atoi(argv[1]);
  int listen_sock = Sock::Socket();
  Sock::Bind(listen_sock,port);
  Sock::Listen(listen_sock);

  //在该数组当中的元素，如果是>=0的，表示需要让select监视该fd上的读事件是否就绪,如果<0表示该位置无效
  int read_array[WATCH_MAX] = {0};
  //将数组所有位置初始化为无效 (-1)
  for(int i = 0;i<WATCH_MAX;i++)
    read_array[i] = INVALID_FD;
  //最初，先将监听套接字放在read_array数组的0位置，表示需要让select服务器监视监听套接字上的读取是否就绪
  read_array[0] = listen_sock;

  //2.提供服务
  for(;;)
  {
     fd_set r_fds;//需要select关心的读取文件描述符集
     FD_ZERO(&r_fds); 
     //由于select采用的是输入输出型的参数来表示：用户告诉内核要关心哪些fd上的事件 内核告诉用户你让我关心的哪些fd上的事件就绪了
     //遍历read_array数组,将要监视读取事件的fd设置到fd集当中
    int max_fd = read_array[0];//记录当前要监视的fd当中，最大的fd
    for(int i = 0;i<WATCH_MAX;i++) //注意：要从0开始，因为0要设置到读取文件描述符集当中
    {
        if(read_array[i] != INVALID_FD)
        {
            FD_SET(read_array[i],&r_fds);
            max_fd = max(max_fd,read_array[i]);
        }
    }
    //函数原型：
    //int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
    // struct timeval {
    //        long    tv_sec;         /* seconds */
    //        long    tv_usec;        /* microseconds */
    //    };
    //若timeout参数为空指针：阻塞等待,直到需要监视的fd上有事件就绪 
    //{0,0}:非阻塞等待，不管监视的fd是否有事件就绪都直接返回
    //{特定的时间}：在该时间内阻塞等待，若在该时间内有事件就绪：返回剩余时间，否则超时返回(0)
    int n = select(max_fd+1,&r_fds,nullptr,nullptr,nullptr); 
    switch(n)
    {
        case 0:
            //cout <<"超时返回" << endl;
            LOG(NOMAL) <<"超时返回" << endl;
            break;
        case -1:
            LOG(WARMING) <<"select error " << endl;
            break;
        default: //有fd上的事件就绪了
            //由于并不知道是哪个fd上的事件就绪，所以还需要轮询检测
            for(int i = 0;i<WATCH_MAX;i++)
            {
              //先检测是否是有效的fd
              if(read_array[i] != INVALID_FD)
              {
                  //检测该fd上的事件是否就绪
                  if(FD_ISSET(read_array[i],&r_fds))
                  {
                    //监听套接字 or 和客户端建立连接的套接字有读取事件就绪
                    if(read_array[i] == listen_sock)
                    { 
                      int new_fd = Sock::Accept(listen_sock);
                      int pos = 1;
                      while(pos < WATCH_MAX && read_array[pos] != INVALID_FD)
                        pos++;

                      if(pos == WATCH_MAX)
                      {
                        LOG(WARMING) <<"监听读事件的数组已满！不能再监听新连接" << "\n";
                        close(new_fd);
                      }
                      else 
                      {
                        LOG(NOMAL) <<"new_fd:" << new_fd <<"被放到read_array数组的"<< pos <<"位置" <<"\n";
                        read_array[pos] = new_fd;
                      }
                    }
                    else 
                    {
                      char buf[1024]  = {0};
                      ssize_t n = read(read_array[i],buf,sizeof(buf)-1);
                      if(n == 0 || n < 0)
                      {
                        LOG(NOMAL) <<"已经将fd："<<read_array[i]<<"从read_array的"<<i<<"位置移除并关闭连接" << endl;
                        close(read_array[i]);
                        read_array[i] =INVALID_FD;
                      }
                      else 
                      {
                        buf[n] = '\0';
                        LOG(NOMAL) <<"server #" <<buf << endl;
                      }
                    }
                  }
              }
            }
            break;
    }

    
  }
  close(listen_sock); //最后记得关闭
  return 0;
}
